/*
 * Copyright 2000-2006 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.intellij.gwt;

import com.intellij.ide.IdeBundle;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.JarFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.libraries.LibraryUtil;
import com.intellij.gwt.configuration.GwtConfigurationEditor;
import com.intellij.gwt.make.GwtCompilerPaths;
import com.intellij.gwt.module.index.GwtFilesIndex;
import com.intellij.gwt.i18n.GwtI18nUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.io.File;

public class GwtConfiguration implements ApplicationComponent, Configurable, JDOMExternalizable {

  public static Icon SMALL_ICON = IconLoader.getIcon("/icons/google-small.png");
  public static Icon LARGE_ICON = IconLoader.getIcon("/icons/google-large.png");
  @NonNls private static final String CLASS_FILE_EXTENSION = ".class";
  @NonNls private static final String GWT_USER_JAR = "gwt-user.jar";
  @NonNls private static final String GWT_DEV_WINDOWS_JAR = "gwt-dev-windows.jar";
  @NonNls private static final String GWT_DEV_LINUX_JAR = "gwt-dev-linux.jar";
  @NonNls private static final String GWT_DEV_MAC_JAR = "gwt-dev-mac.jar";
  @NonNls private static final String GWT_PATH_ATTRIBUTE = "gwtPath";
  @NonNls private static final String GWT_SCRIPT_OUTPUT_STYLE_ATTRIBUTE = "gwtScriptOutputStyle";
  private GwtConfigurationEditor myEditor;
  private String myGwtPath = "";
  private GwtJavaScriptOutputStyle myOutputStyle = GwtJavaScriptOutputStyle.DETAILED;
  private boolean myVersion1_0 = false;

  public static GwtConfiguration getInstance() {
    return ApplicationManager.getApplication().getComponent(GwtConfiguration.class);
  }

  public String getGwtPath() {
    return myGwtPath;
  }

  public boolean isValidConfiguration() {
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      return true;
    }
    return myGwtPath != null && myGwtPath.length() != 0;
  }

  public JComponent createComponent() {
    myEditor = new GwtConfigurationEditor();
    return myEditor.getMainPanel();
  }

  public boolean isModified() {
    return !myGwtPath.equals(myEditor.getGwtInstallationPath()) ||
           myOutputStyle != myEditor.getOutputStyle();
  }

  public void apply() throws ConfigurationException {
    final boolean wasValid = isValidConfiguration();
    final String gwtPath = myEditor.getGwtInstallationPath();

    checkClass("com.google.gwt.dev.GWTCompiler", getDevJarPath(gwtPath));
    checkClass("com.google.gwt.dev.GWTShell", getDevJarPath(gwtPath));
    checkClass("com.google.gwt.core.client.GWT", getUserJarPath(gwtPath));

    if (myOutputStyle != myEditor.getOutputStyle() || !Comparing.equal(myGwtPath, gwtPath)) {
      //todo[nik] gwt compiler won't recompile sources after changing output style so... 
      FileUtil.delete(GwtCompilerPaths.getOutputRoot());
    }
    updateGwtSDK(gwtPath);
    myOutputStyle = myEditor.getOutputStyle();
    if (!wasValid && isValidConfiguration()) {
      ApplicationManager.getApplication().invokeLater(new Runnable() {
        public void run() {
          final Project[] projects = ProjectManager.getInstance().getOpenProjects();
          for (Project project : projects) {
            final GwtFilesIndex gwtFilesIndex = GwtFilesIndex.getInstance(project);
            ProgressManager.getInstance().runProcessWithProgressSynchronously(gwtFilesIndex.getBuildIndicesRunnable(),
                                                                              GwtBundle.message("progress.title.building.gwt.indices"), false, project);
            gwtFilesIndex.registerListeners();
          }
        }
      });
    }
  }

  private void updateGwtSDK(final String gwtPath) {
    myGwtPath = gwtPath;
    if (myGwtPath != null && myGwtPath.length() > 0) {
      final VirtualFile jarFile = findJarFile(getUserJarPath());
      if (jarFile != null) {
        myVersion1_0 = !LibraryUtil.isClassAvailableInLibrary(new VirtualFile[]{jarFile}, GwtI18nUtil.CONSTANTS_INTERFACE_NAME);
      }
    }
  }


  public boolean isVersion1_0() {
    return myVersion1_0;
  }

  public void reset() {
    myEditor.setGwtInstallationPath(myGwtPath);
    myEditor.setOutputStyle(myOutputStyle);
  }

  public void disposeUIResources() {
    myEditor = null;
  }

  public void readExternal(Element element) throws InvalidDataException {
    String path = JDOMExternalizer.readString(element, GWT_PATH_ATTRIBUTE);
    updateGwtSDK(path == null ? "" : path);
    final String styleId = JDOMExternalizer.readString(element, GWT_SCRIPT_OUTPUT_STYLE_ATTRIBUTE);
    final GwtJavaScriptOutputStyle style = GwtJavaScriptOutputStyle.byId(styleId);
    if (style != null) {
      myOutputStyle = style;
    }
  }

  public void writeExternal(Element element) throws WriteExternalException {
    JDOMExternalizer.write(element, GWT_PATH_ATTRIBUTE, myGwtPath);
    JDOMExternalizer.write(element, GWT_SCRIPT_OUTPUT_STYLE_ATTRIBUTE, myOutputStyle.getId());
  }

  private static void checkClass(final @NonNls String className, final String jarPath) throws ConfigurationException {
    final VirtualFile jarFile = findJarFile(jarPath);
    if (jarFile == null) {
      throw new ConfigurationException(IdeBundle.message("error.file.not.found.message", jarPath), GwtBundle.message("config.nogwt.title"));
    }
    final VirtualFile classFile = jarFile.findFileByRelativePath(className.replace('.', '/') + CLASS_FILE_EXTENSION);
    if (classFile == null) {
      throw new ConfigurationException(GwtBundle.message("error.class.not.found.in.jar", className), GwtBundle.message("config.nogwt.title"));
    }
  }

  @Nullable
  private static VirtualFile findJarFile(final String jarPath) {
    return JarFileSystem.getInstance().refreshAndFindFileByPath(FileUtil.toSystemIndependentName(jarPath) + JarFileSystem.JAR_SEPARATOR);
  }

  @NonNls @NotNull
  public String getComponentName() {
    return "GWT.Configuration";
  }

  public void initComponent() {
  }

  public void disposeComponent() {
  }

  public String getDisplayName() {
    return GwtBundle.message("google.web.toolkit.title");
  }

  public Icon getIcon() {
    return LARGE_ICON;
  }

  @Nullable
  @NonNls
  public String getHelpTopic() {
    return "preferences.GWT";
  }

  private static String getUserJarPath(String base) {
    return base + File.separator + GWT_USER_JAR;
  }

  private static String getDevJarPath(String base) {
    final String jarName;
    if (SystemInfo.isWindows) {
      jarName = GWT_DEV_WINDOWS_JAR;
    }
    else if (SystemInfo.isMac) {
      jarName = GWT_DEV_MAC_JAR;
    }
    else {
      jarName = GWT_DEV_LINUX_JAR;
    }
    return base + File.separator + jarName;
  }

  public String getUserJarPath() {
    return getUserJarPath(myGwtPath);
  }

  public String getDevJarPath() {
    return getDevJarPath(myGwtPath);
  }

  public GwtJavaScriptOutputStyle getOutputStyle() {
    return myOutputStyle;
  }
}
